/*
  `  Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_MISC_HPP
#define CVML_MISC_HPP

#include <iostream> 
#include <fstream>

typedef std::string String;
typedef const String& ConstString;
typedef const char* PChar;

struct CVMLException {
	std::string msg;
    CVMLException(const char* _msg)
        : msg(_msg)
    { }
	CVMLException(const std::string& _msg)
        : msg(_msg)
    { }
};

	void Error(const char* x) {
	printf("Error: %s\n", x);
    throw CVMLException(x);
}

void Error(ConstString s) {
	Error(s.c_str());
}

template<typename Map_T, typename Key_T>
bool MapContains(const Map_T& map, const Key_T& key) {
    return map.find(key) != map.end();
}

template<typename Vector_T, typename Val_T>
int IndexOf(const Vector_T& vec, const Val_T& val) {    
	Vector_T::const_iterator i = std::find(vec.begin(), vec.end(), val);
	if (i == vec.end()) 
		return -1;
	int r = i - vec.begin();
	Assert(r >= 0);
	Assert((size_t)r < vec.size());
	return r;
}

template<typename Vector_T, typename Val_T>
bool Contains(const Vector_T& vec, const Val_T& val) {
	return IndexOf(vec, val) != -1;
}

template<typename Vector_T, typename Elem_T>
void DeleteFromVector(Vector_T vec, Elem_T elem) {
    std::remove(vec.begin(), vec.end(), elem);
}

template<typename Vector_T>
void DeleteNulls(Vector_T vec) {
    std::remove(vec.begin(), vec.end(), NULL);
}

template<typename Iter_T>
void DeletePointers(Iter_T begin, Iter_T end) {
    while (begin != end)
        delete(*begin++);
}

template<typename Vector_T>
void DeletePointers(Vector_T vec) {
	DeletePointers(vec.begin(), vec.end());
	vec.clear();
}

int StrToInt(ConstString s) 
{
    std::stringstream ss(s);
    int r = 0;
    ss >> r;
    return r;
}

String ReadFile(ConstString sName) {
	std::stringstream out;
	std::ifstream in(sName.c_str());
	std::string line;
	while (!in.eof())
    {
		getline(in, line);
		out << line << std::endl;
    }
	return out.str();
}

void PrintStringToFile(ConstString s, FILE* f) {
	fprintf(f, "%s", s.c_str());
}

void PrintStringToFile(ConstString s, ConstString fname) {
	FILE* f = fopen(fname.c_str(), "w");
	if (f == NULL)
		Error("could not open file " + s);
	PrintStringToFile(s, f);	
	fclose(f);
}

String ToStr(PChar x) {
	return x;
}

String ToStr(String s) {
	return s;
}

String ToStr(double x) {
	std::stringstream ss;
	ss << x;
	String r;
	ss >> r;
	return r;
}

String ToStr(char c) {
	String r = " ";
	r[0] = c;
	return r;
}

String ToStr(bool b) {
	if (b) return "true"; 
	else return "false";
}

String EscapeStr(ConstString s) {	
	String r;
	for (size_t i=0; i < s.size(); ++i) {
		switch (s[i])
		{
		case '\\':
			r.push_back('\\');
			r.push_back('\\');
			break;
		case '\0':
			r.push_back('\\');
			r.push_back('0');
			break;	
		case '\t':
			r.push_back('\\');
			r.push_back('t');
			break;	
		case '\n':
			r.push_back('\\');
			r.push_back('n');
			break;	
		case '\r':
			r.push_back('\\');
			r.push_back('r');
			break;	
		case '\b':
			r.push_back('\\');
			r.push_back('b');
			break;	
		case '\'':
			r.push_back('\\');
			r.push_back('\'');
			break;	
		case '\"':
			r.push_back('\\');
			r.push_back('\"');
			break;	
		default:
			r.push_back(s[i]);
			break;
		}
	}
	return r;
}

String ToStr(int n) {
	std::stringstream ss("");
	ss << n;
	std::string s;
	ss >> s;
	return s;
}

String ToStr(size_t x) {
	return ToStr((int)x);
}

int HexStrToInt(ConstString s)
{
    int r = 0;
	std::stringstream(s) >> std::hex >> r;
    return r;
}

char StrToChar(ConstString s)
{
    std::stringstream ss(s);
    char r = '\0';
    ss >> r;
    return r;
}

bool StringBeginsWith(String src, String sub) {
    if (sub.size() > src.size())
        return false;
    return src.substr(0, sub.size()) == sub;
}

void Assert(bool b, String s = "assertion failed") 
{
    if (!b) {
        Error(s);
    }
}

void Todo() {
	Assert(false);
}

int IndexOfChar(ConstString s, char c) {
	for (size_t i=0; i < s.size(); ++i) 
		if (s[i] == c)
			return i;
	return -1;
}

String SubStrToChar(ConstString s, char c) {
	int n = IndexOfChar(s, c);
	if (n < 0)
		return s;
	return s.substr(0, n);
}

int PosOfExt(ConstString filename) {
    int n = filename.size();
    int off = n - 1;
    while (off >= 0 && filename[off] != '.') 
        --off;
    return off;
}

String GetExt(ConstString filename) {
    int off = PosOfExt(filename);
    if (off < 0)
        return "";
    return filename.substr(off, filename.size() - off);
}
        
int PosOfBaseFilename(ConstString filename) {
    int off = filename.size() - 1;
    while (off >= 0 && filename[off] != '\\' && filename[off] != '/') 
        --off;
    if (off < 0) 
        return 0;
    else
        return off + 1;
}

bool IsWSpaceChar(char c) {
    switch (c) {
        case ' ':
        case '\t':
        case '\n':
        case '\r':
            return true;
        default:
            return false;
    }
}

String TrimEnd(ConstString s) {
    int n = s.size() - 1;
    while (n >= 0 && IsWSpaceChar(s[n])) 
        --n;
    if (n < 0) 
        return "";
    else
        return s.substr(0, n + 1);
}

String TrimBegin(ConstString s) {
    size_t n = 0;
    while (n < s.size() && IsWSpaceChar(s[n])) 
        ++n;
    return s.substr(n);
}

String Trim(ConstString s) {
    return TrimBegin(TrimEnd(s));
}

String StripPath(ConstString filename) {
    int n = PosOfBaseFilename(filename);
    return filename.substr(n);
}

String GetDir(ConstString filename) {
	int n = PosOfBaseFilename(filename);
	return filename.substr(0, n);
}

String StripExt(String filename) {
    int n = PosOfExt(filename);
    if (n < 0)
        return filename;
    return filename.substr(0, n);
}

String StripPathAndExt(String filename) {
    return StripPath(StripExt(filename));
}

FILE* OUTPUT = stdout;

void RedirectOutput(const std::string& file)
{
    fflush(OUTPUT);
    OUTPUT = fopen(file.c_str(), "w");
    if (OUTPUT == NULL)
        Error("could not redirect output to " + file);
}

void RestoreOutput() 
{
    fflush(OUTPUT);
    fclose(OUTPUT);
    OUTPUT = stdout;
}

template<typename T>
struct NonNull
{
	T* ptr;
	
	NonNull(T* x)
	{
		Assert(x != NULL);
		ptr = x;
	}

	T* operator->() { 
		return ptr;
	}

	operator T*() {
		return ptr;
	}
};

struct Strings 
{
	std::vector<String> strings;

	char* ToPChar() {
		size_t size = 0;
		for (int i = 0; i < Count(); ++i) {
			size += (*this)[i].size();
		}
		char* r = (char*)malloc(size);
		int k = 0;
		for (int i = 0; i < Count(); ++i) {
			String& s = (*this)[i];
			for (size_t j=0; j < s.size(); ++j) {
				r[k++] = s[j];
			}
		}
		r[k] = '\0';
		return r;
	}

	int Count() { 
		return strings.size();
	}

	String& operator[](int i) {
		return strings[i];
	}

	void Push(ConstString s) {
		strings.push_back(s);
	}

	String Pull() {
		String r = Top();
		Pop();
		return r;
	}

	void Pop() {
		strings.pop_back();
	}

	String& Top() {
		return strings.back();
	}

	bool Empty() {
		return Count() == 0;
	}

	void Remove(int n) {
		strings.erase(strings.begin() + n);
	}
};

String GenSym() {
	static int n = 0;
	String s("$");
	return s + ToStr(n++);
}

String GenSym(ConstString s) {
	return GenSym() + "_" + s;
}

bool IsGenSym(String s) {
	return s.size() > 0 && s[0] == '$';
}

bool IsNumeric(char c) {
	return (c >= '0' && c <= '9');
}

bool IsLetter(char c) {
	return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}

bool IsAlphaNumeric(char c) {
	return IsNumeric(c) || IsLetter(c) || c == '_';
}

#endif
